package ui;

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.TransferHandler;
import javax.swing.table.AbstractTableModel;

import core.Presentable;
import core.SetIndex;
import core.SetRecord;
import core.SongIndex;
import core.SongRecord;
import core.Utilities;


public class SetEditor extends JPanel implements ActionListener {
	private static final long serialVersionUID = 1L;
	private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
	
	@SuppressWarnings("unused")
	private Presenter parent;
	protected SetRecord record = null;
	private SetIndex setIndex = null;
	private SongIndex songIndex = null;
	private JTable songTable;
	private AbstractTableModel tableModel;
	private boolean newSet = false;
	
	private SetEditor(Presenter p) {
		super(new GridBagLayout());
		parent = p;
	}
	
	/**
	 * Constructor for a blank SetEditor. Creates a new SetRecord
	 * from the given SetIndex.
	 * @param si	existing set index
	 * @param p		parent frame
	 */
	public SetEditor(SetIndex setInd, SongIndex songInd, Presenter p) {
		this(p);
		setName("New Set");
		setIndex = setInd;
		songIndex = songInd;
		record = setInd.newRecord();
		initComponents();
		dateField.setValue(record.getField(SetRecord.DATE));
		newSet = true;
	}
	
	/**
	 * Constructor for a SetEditor to edit an existing SetRecord.
	 * @param sr	existing set record
	 * @param p		parent frame
	 */
	public SetEditor(SetRecord sr, SongIndex songInd, Presenter p) {
		this(p);
		record = sr;
		songIndex = songInd;
		initComponents();
		
		descriptionField.setText((String)record.getField(SetRecord.DESCRIPTION));
		leaderField.setText((String)record.getField(SetRecord.LEADER));
		dateField.setValue(record.getField(SetRecord.DATE));
		refreshTitle();
	}
	
	/**
	 * Handles save/close actions.
	 */
	public void actionPerformed(ActionEvent e) {
		if (e.getActionCommand().equalsIgnoreCase("save")) {
			if (newSet) {		// if this editor created a new set not already in the index
	    		setIndex.add(record);
	    		newSet = false;
			}
			
			record.setField(SetRecord.DESCRIPTION, descriptionField.getText());
	    	record.setField(SetRecord.LEADER, leaderField.getText());
	    	record.setField(SetRecord.DATE, dateField.getValue());
	    	
	    	refreshTitle();
		}
		else if (e.getActionCommand().equalsIgnoreCase("close")) {
		}
	}
	
	/**
	 * Inserts a presentable item into the set.
	 * @param p		a presentable item
	 */
	public void addItem(Presentable p) {
		record.add(p);
		tableModel.fireTableStructureChanged();
	}
	
	/**
	 * Inserts a presentable item into the set
	 * at the given index
	 * @param i		index
	 * @param p		a presentable item
	 */
	public void addItem(int i, Presentable p) {
		record.add(i, p);
		tableModel.fireTableRowsInserted(i, i);
	}
	
	/**
	 * Removes the item at given index in the record.
	 * @param i		index
	 */
	private void removeItem(int i) {
		synchronized (record) {
			record.remove(i);
			tableModel.fireTableRowsDeleted(i, i);
		}
	}
	
	private void refreshTitle() {
		setName(record.getField(SetRecord.DESCRIPTION) + " [" + dateField.getText() + "]");
	}
	
	private void initComponents() {
    	GridBagConstraints c = new GridBagConstraints();
    	
        cutItem = new JMenuItem();
        copyItem = new JMenuItem();
        pasteItem = new JMenuItem();
        descriptionLabel = new JLabel();
        descriptionField = new JTextField();
        dateLabel = new JLabel();
        dateField = new JFormattedTextField(dateFormat);
        dateField.setEditable(false);
        leaderLabel = new JLabel();
        leaderField = new JTextField();
        songScrollPane = new JScrollPane();

        cutItem.setText("Cut");
        copyItem.setText("Copy");
        pasteItem.setText("Paste");
        
        /* For later releases:
        editPopupMenu = new JPopupMenu();
        editPopupMenu.add(cutItem);
        editPopupMenu.add(copyItem);
        editPopupMenu.add(pasteItem);
        lyricsTextArea.setComponentPopupMenu(editPopupMenu);
		*/
        
		makeSongTable();
		
        descriptionLabel.setText("Description");
        dateLabel.setText("Date");
        leaderLabel.setText("Leader");
        songScrollPane.setViewportView(songTable);
        
        c.insets.left = c.insets.right = c.insets.top = 5;
        c.fill = GridBagConstraints.HORIZONTAL;
        c.gridy = 0;
        c.gridx = 0; add(descriptionLabel, c);
        c.weightx = 0.1;
        c.gridwidth = GridBagConstraints.REMAINDER;
        c.gridx = 1; add(descriptionField, c);
        
        c.insets.top = 2;
        c.gridwidth = 1;
        c.gridy = 1;
        c.weightx = 0;
        c.gridx = 0; add(dateLabel, c);
        c.weightx = 0.1;
        c.gridx = 1; add(dateField, c);
        
        c.weightx = 0;
        c.gridx = 2; add(leaderLabel, c);
        c.weightx = 0.1;
        c.gridx = 3; add(leaderField, c);
        
        c.gridwidth = GridBagConstraints.REMAINDER;
        c.gridy = 3;
        c.weightx = 0;
        c.weighty = 0.1;
        c.gridheight = 2;
        c.fill = GridBagConstraints.BOTH;
        c.insets.bottom = 5;
        c.gridx = 0; add(songScrollPane, c);
        
        
        setPreferredSize(new Dimension(400, 320));
        setVisible(true);
    }
	
    @SuppressWarnings("serial")
	private void makeSongTable() {
    	
    	tableModel = new AbstractTableModel() {
    		public int getColumnCount() {
    			// TODO: remove arbitrary constant when features implemented
    			// KLUDGE: don't display KEY, SLIDE ORDER, TAGS or BACKGROUND IMAGE fields - unimplemented
    			return 3;
    		}
    		public String getColumnName(int column) {
    			return SongRecord.fieldNames[column];
    		}
    		public int getRowCount() {
    			if (record == null)
    				return 0;
    			
    			return record.getItemCount();
    		}
    		public Object getValueAt(int rowIndex, int columnIndex) {
    			Presentable p = record.getItem(rowIndex, songIndex);
    			if (p instanceof SongRecord)
    				return ((SongRecord)p).getField(columnIndex);
    			
    			return null;
    		}
    		public Class<?> getColumnClass(int columnIndex) {
    			return String.class;
    		}
    	};
    	
    	songTable = new JTable(tableModel);
		songTable.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
		
		songTable.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				switch (e.getKeyCode()) {
					case KeyEvent.VK_ENTER:
						for (int i : songTable.getSelectedRows()) {
							Presentable p = record.getItem(i, songIndex);
							
							if (p instanceof SongRecord)
								parent.openSongEditorTab((SongRecord)p);
						}
						e.consume();
						break;
						
					case KeyEvent.VK_DELETE: {
						int[] selRows = Utilities.getSortedSelectionRows(songTable);
						
						for (int i=selRows.length-1; i>=0; i--)
							removeItem(selRows[i]);
						break;
					}
					
					// move song to front
					case KeyEvent.VK_UP: case KeyEvent.VK_KP_UP:
						if (e.isControlDown()) {
							int from = songTable.getSelectedRow();
							if (from > 0) {
								int to = from-1;
								record.moveItem(from, to);
								tableModel.fireTableCellUpdated(from, to);
//								songTable.setRowSelectionInterval(to, to);
							}
						}
						break;
						
					// move song to back
					case KeyEvent.VK_DOWN: case KeyEvent.VK_KP_DOWN:
						if (e.isControlDown()) {
							int from = songTable.getSelectedRow();
							if (from < songTable.getRowCount()-1) {
								int to = from+1;
								record.moveItem(from, to);
								tableModel.fireTableCellUpdated(from, to);
//								songTable.setRowSelectionInterval(to, to);
							}
						}
						break;
				}
			}
		});
		songTable.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				if (e.getClickCount() == 2) {
					Presentable p = record.getItem(songTable.convertRowIndexToModel(songTable.getSelectedRow()), songIndex);
					if (p instanceof SongRecord)
						parent.openSongEditorTab((SongRecord)p);
				}
			}
		});
		
//		songTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
		songTable.setFillsViewportHeight(true);
//		songTable.getColumnModel().getColumn(0).setPreferredWidth(tableView.getViewport().getViewSize().width);
		for (int i = 0; i < songTable.getColumnModel().getColumnCount(); i++) {
		    songTable.getColumnModel().getColumn(i).setPreferredWidth(160);
		}
		
		songTable.setDragEnabled(true);
		songTable.setTransferHandler(new EditorTransferHandler());
    }
    
    /**
     * This transfer handler exports SongSelections and also handles
     * internal drag moves for rearranging songs within the set.
     * @author john
     *
     */
    private class EditorTransferHandler extends TransferHandler {
    	
    	private int[] rows = null;
    	
    	public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
    		for (int i=0; i<transferFlavors.length; i++) 
    			if (SongSelection.flavor.equals(transferFlavors[i])) {
    				return true;
    			}
    		return false;
    	}
    	
    	@SuppressWarnings("unchecked")
    	public boolean importData(JComponent comp, Transferable t) {
    		if (canImport(comp, t.getTransferDataFlavors())) {
    			try {
    				JTable table = (JTable)comp;
    				int selRow = table.getSelectedRow();
    				final int max = table.getModel().getRowCount();
    				
//    				System.out.println("drop row: " + selRow);
    				
    				// do nothing if user is dropping back within the same range
    				if (rows != null && selRow >= rows[0] && selRow <= rows[rows.length-1]) {
//    					System.out.println(" ignoring");
    					rows = null;
    				}
    				// user is rearranging songs by dragging
    				else if (rows != null){
//    					System.out.println(" rearranging");
    					// check out of bounds
    					if (selRow < 0)
							selRow = max-1;
    					
    					// if moving songs down the list, move in reverse order
    					if (selRow > rows[0])
    						for (int i=rows.length-1; i>=0; i--)
	    						record.moveItem(rows[i], selRow--);
    					else
	    					for (int i=0; i<rows.length; i++)
	    						record.moveItem(rows[i], selRow++);
    					
    					tableModel.fireTableRowsUpdated(0, tableModel.getRowCount()-1);
    				}
    				// user is dragging in new songs
    				else {
//    					System.out.println(" adding");
    					List<SongRecord> sr = (List<SongRecord>)t.getTransferData(SongSelection.flavor);
    					
    					// if dropping outside of rows, add to the end
    					if (selRow < 0)
    						selRow = max;
    					else {
    						selRow++;	// add items to the row after the drop row
    						if (selRow > max)
    							selRow = max;
    					}
    					
    					for (int i=0; i<sr.size(); i++)
    						SetEditor.this.addItem(selRow++, sr.get(i));
    				}
    				return true;
    			}
    			catch(UnsupportedFlavorException ex) {}
    			catch(IOException ex) {}
    		}
    		
    		return false;
    	}
    	
    	@Override
    	protected Transferable createTransferable(JComponent c) {
    		JTable table = (JTable)c;
    		int[] modelRows = Utilities.mapSelectionRowsToModel(table);
    		List<SongRecord> selection = new ArrayList<SongRecord>(modelRows.length);
    		for (int i : modelRows) {
    			Presentable item = record.getItem(i, songIndex);
    			if (item instanceof SongRecord) {
    				selection.add((SongRecord)item);
    			}
    		}
    		
    		// store selection rows for dropping songs back on self
    		rows = table.getSelectedRows();
//    		System.out.println("selected rows " + java.util.Arrays.toString(rows));
    		return new SongSelection(selection);
    	}
    	
    	protected void exportDone(JComponent source, Transferable data, int action) {
    		rows = null;
    	}
    	
    	@Override
    	public int getSourceActions(JComponent c) {
    		return MOVE;
    	}
    }

    
    private JFormattedTextField dateField;
    private JLabel dateLabel;
    private JMenuItem copyItem;
    private JTextField leaderField;
    private JLabel leaderLabel;
    private JMenuItem cutItem;
    //private JPopupMenu editPopupMenu;
    private JScrollPane songScrollPane;
    private JMenuItem pasteItem;
    private JTextField descriptionField;
    private JLabel descriptionLabel;
}
